package terminal;

import console.manager.TaskManager;
import console.ConsoleException;
import java.io.*;

public class Kermit {
	OutputStream outputStream;
	static final int KERMIT_FILE_CHUNK_SIZE=0x5e;
	static final byte KERMIT_ACK='Y';
	static final byte KERMIT_END_OF_BATCH='B';
	static final byte KERMIT_FILE_DATA='D';
	static final byte KERMIT_FILE_HEADER='F';
	static final byte KERMIT_INITIALIZATION='S';
	static final byte KERMIT_NEGATIVE_ACKNOWLEDGE='N';
	static final byte KERMIT_END_OF_FILE='Z';
	static final byte KERMIT_NONE='X';
	public static final byte KERMIT_START_OF_HEADER=0x01;
	static final byte KERMIT_END_OF_PACKET=0x0d;
	public boolean escaped = false;
	boolean gotDataLength = false;
	static int sequence;
	int sequenceIn;
	boolean fileDone;
	int fileProgress;
    int taskId;
	int fileChunkLength;
	int dataIndex;
	int dataLength;
	byte[] data;
	boolean gotSequenceIn=false;
	boolean gotCommand=false;
	boolean gotCheckSum=false;
	int sum,response,command;
	String fileName;
	static int fileLock=0;
	static int fileLockCounter=0;
	byte[] fileData;

	public Kermit(OutputStream outputStream) {
		this.outputStream=outputStream;
		data=new byte[0];
		fileName="MON_PULSE";
		fileData=new byte[0];
	}

	int[] createPacket(byte[] data,byte type,int sequence) {
		/*
			1 SOH
			2 length
			3 sequence
			4 command type
			... data
			5 check sum
			6 end of packed
		*/
		final int PACKET_OVERHEAD = 6;
		int[] packet = new int[data.length+PACKET_OVERHEAD];
		int i,j,sum;
		int len;
		if(data.length>KERMIT_FILE_CHUNK_SIZE) {
			len=KERMIT_FILE_CHUNK_SIZE;
		} else {
			len=data.length;
		}
		i=0;
		packet[i++]=KERMIT_START_OF_HEADER;
		sum=packet[i++]=0x23+len;
		sum+=packet[i++]=0x20+(sequence&0x3f);
		sum+=packet[i++]=type;
		for(j=0;j<len;j++) {
			sum+=packet[i++]=data[j];
		}
		sum = ((sum&0xc0)/64)+sum;
		sum &= 0x3f;
		sum += 0x20;
		packet[i++]=sum;
		packet[i++]=KERMIT_END_OF_PACKET;
		return(packet);
	}

	void sendPacket(int[] packet) {
		byte[] arr;
		arr = new byte[packet.length];
//		System.out.println();
//		System.out.print("\t\t\t");
		for(int i=0;i<packet.length;i++) {
			arr[i]=(byte)packet[i];
			//System.out.print("-"+Integer.toHexString(packet[i]&0xff));
//            if(packet[i]==1) {
//                System.out.print('S');
//            } else {
//			    System.out.print((char)(packet[i]&0xff));
//            }
		}
//		System.out.println();
		try {
			outputStream.write(arr);
		} catch (IOException e) { System.out.println("stream write error"); }
	}
	
	byte[] stripHash(byte[] currentData,byte[] data) {
			int i,j;
			byte[] newFileData;
			for(i=0,j=0;j<data.length;i++,j++) { /* strip hash */
				if(data[j]=='#') {
					j++;
					if(j>=data.length) break;
					if(((data[j]&0x7f)>62)&&((data[j]&0x7f)<96)) {
						data[j]^=0x40;
					}
				}
				data[i]=data[j];
			}
			newFileData=new byte[currentData.length+i];
			for(j=0;j<currentData.length;j++) {
				newFileData[j]=currentData[j];
			}
			for(j=0;j<i;j++) {
				newFileData[currentData.length+j]=data[j];
			}
			return(newFileData);
	}

	public int handleIncomming(byte[] buf,int i,int numBytes) {
		int offset=0;
        try {
		while((i+offset)<numBytes) {
//			System.out.println("handler loop i is:"+i+" offset is:"+offset);
			if(!gotDataLength) {
				sum+=dataLength=buf[i+offset++];
				dataLength-=0x23;
				dataIndex=0;
				gotDataLength=true;
				data=new byte[dataLength];
//				System.out.println();
//				System.out.print("response:"+(char)response);
//				System.out.print(",length:"+dataLength);
				if(dataLength>100) {
					throw(new RuntimeException("dataLength too long"));
				}
				continue;
			}
			if(!gotSequenceIn) {
				sum+=sequenceIn=buf[i+offset++];
				sequenceIn-=0x20;

//				System.out.print(",sequence:"+(sequence&0x3f)+ " " + "in:" + sequenceIn);
				if(sequenceIn!=(sequence&0x3f)) {
//					System.out.print(",sequence("+(sequence&0x3f)+")!=sequenceIn("+sequenceIn+")");
//                    if(((sequenceIn+1)&0x3f)==(sequence&0x3f)) {
//    					sequence-=1; /* resend ack */
//                    }
//					sequence-=2;
//					throw(new RuntimeException("out of sequence"));
				}
				gotSequenceIn=true;
				continue;
			}
			if(!gotCommand) {
				sum+=command=buf[i+offset++];
//				System.out.print(",command:"+(char)command);
				gotCommand=true;
				continue;
			}
			if(dataIndex<dataLength) {
				sum+=data[dataIndex++]=buf[i+offset++];
				continue;
			}
			if(!gotCheckSum) {
				sum = ((sum&0xc0)/64)+sum;
				sum &= 0x3f;
				sum += 0x20;
				if(buf[i+offset++]!=sum) {
					/* throw?? --> NACK */
					throw(new RuntimeException("bad check sum"));
//				    System.out.print(",check not ok");
                    /* TODO set back checksum flag */
				} else {
//				    System.out.print(",check ok");
                }
				gotCheckSum=true;
				continue;
			}
			if(buf[i+offset++]!=KERMIT_END_OF_PACKET) {
				/* throw?? --> NACK */
					System.out.print(" no EOP");
					throw(new RuntimeException("no end of packet"));
			}
			escaped=false;
			switch(command) {
				case KERMIT_END_OF_BATCH:
					response=KERMIT_ACK;
  		            TaskManager.setState(taskId,TaskManager.STATE_DONE,"receive complete");
					break;
				case KERMIT_FILE_DATA:
					fileData=stripHash(fileData,data);
  		            TaskManager.setProgress(taskId,fileData.length);
					response=KERMIT_ACK;
					break;
				case KERMIT_END_OF_FILE:
					response=KERMIT_ACK;
					break;
				case KERMIT_FILE_HEADER:
					response=KERMIT_ACK;
//					selected_monitor_command=find_monitor_command(kermit_str);
					break;
				case KERMIT_INITIALIZATION:
					System.out.println("received S, zero sequence");
					sequence=0; /* jiffy */
					fileDone=false;
					fileProgress=0;
					response=KERMIT_ACK;
					break;
				case KERMIT_NEGATIVE_ACKNOWLEDGE:
					if((response==KERMIT_FILE_DATA)&&(sequence>0)) {
						System.out.println("received N after sending D, dec sequence "+(sequence&0x3f));
						sequence--;
						response=KERMIT_FILE_DATA;
					} else if(response==KERMIT_NEGATIVE_ACKNOWLEDGE) {
						System.out.println("received N after sending N, dec sequence "+(sequence&0x3f));
						sequence--;
						response=KERMIT_ACK;
						response=KERMIT_NONE;
					} else if(response==KERMIT_ACK) {
						System.out.println("received N after sending Y, dec sequence "+(sequence&0x3f));
						//sequence--;
						response=KERMIT_ACK;
					} else {
						response=KERMIT_INITIALIZATION;
					}
					break;
				case KERMIT_ACK:
//					System.out.println("received acknowledge, response is: "+response);
					switch(response) {
						case KERMIT_INITIALIZATION:
							response=KERMIT_FILE_HEADER;
  		                    TaskManager.setStateInfo(taskId,"sending file header");
							break;
						case KERMIT_FILE_HEADER:
							response=KERMIT_FILE_DATA;
  		                    TaskManager.setStateInfo(taskId,"sending file data");
							break;
						case KERMIT_FILE_DATA:
							fileProgress+=fileChunkLength;
							if(fileProgress>=fileData.length) {
  		                        TaskManager.setStateInfo(taskId,"sending end of file");
								response=KERMIT_END_OF_FILE;
								fileProgress=0;
								fileChunkLength=0;
							} else {
								response=KERMIT_FILE_DATA;
							}
                            if(fileProgress>0) {
  		                        TaskManager.setProgress(taskId,fileProgress);
                            } else {
  		                        TaskManager.setProgress(taskId,100,100);
                            }
							break;
						case KERMIT_END_OF_FILE:
							response=KERMIT_END_OF_BATCH;
  		                    TaskManager.setStateInfo(taskId,"sending end of batch");
							break;
						case KERMIT_END_OF_BATCH:
							response=KERMIT_NONE;
//							System.out.println();
//							System.out.println("received Y after sending B, zero sequence");
							sequence=0;
							fileLock=-fileLock;
  		                    TaskManager.setState(taskId,TaskManager.STATE_DONE,"send complete");
							break;
					}
					break;
			}
			switch(response) {
				case KERMIT_INITIALIZATION:
					System.out.println("sending S,  zero sequence");
					sequence=0;
					fileDone=false;
//					System.out.println("sending initialization packet");
					initializationHeader();
//					System.out.println("sent initialization, packet i is"+i+" offset is:"+offset+" numBytes is:"+numBytes);
					break;
				case KERMIT_FILE_HEADER:
//					System.out.println("sending file header");
					fileHeader();
					break;
				case KERMIT_FILE_DATA:
//					System.out.println("sending file data");
					fileData();
					break;
				case KERMIT_ACK:
//					System.out.println(",sending ack");
					ack();
					if(command==KERMIT_END_OF_BATCH) {
						System.out.println("sending Y, after getting B,  zero sequence");
						sequence=0;
						fileDone=false;
						fileLock=-fileLock;
					}
					break;
				case KERMIT_NEGATIVE_ACKNOWLEDGE:
//					System.out.println("sending nack");
					nack();
					return(numBytes);
				case KERMIT_END_OF_FILE:
//					System.out.println("sending end of file");
					endOfFile();
					break;
				case KERMIT_END_OF_BATCH:
//					System.out.println("sending end of batch");
					batchEnd();
					fileDone=false;
					fileChunkLength=0;
					break;
			}
			return(offset);
		}
        } catch (ConsoleException e) {
            System.out.println("console exception " + e.getMessage());
        }
		return(offset);
	}
	public void initializePacket() {
		sum=0;
//		data=new byte[0];
		dataIndex=0;
		dataLength=0;
		command=KERMIT_NONE;
		gotDataLength=false;
		gotSequenceIn=false;
		gotCommand=false;
//		fileDone=false;
		gotCheckSum=false;
//		response=KERMIT_NONE;
//		sequence=0;
//		System.out.println("init packet,  zero sequence");

		escaped=true;
	}
	public void initializationHeader() {
		response=KERMIT_INITIALIZATION;
		sendPacket(createPacket(new byte[0],KERMIT_INITIALIZATION,0));
	}
	public void clearEscaped() {
		escaped=false;
	}
	public void fileHeader() {
		sendPacket(createPacket(fileName.getBytes(),KERMIT_FILE_HEADER,++sequence));
	}
	boolean isCtl(int ch) {
		ch&=0x7f;
		if(((ch>=0)&&(ch<=31))||(ch==127)) {
			return(true);
		} else {
			return(false);
		}
	}
	public void fileData() {
		final int MAX_KERMIT_PACKET_SIZE=50;
		int i;
		int ch;
		int progress;

		progress=fileProgress;
		byte[] packet = new byte[MAX_KERMIT_PACKET_SIZE*2];
		i=0;
		while((i<MAX_KERMIT_PACKET_SIZE)&&(progress<fileData.length)) {
			ch=fileData[progress]&0xff;
			if(isCtl(ch)) {
				if((i+1)>=MAX_KERMIT_PACKET_SIZE) {
					break;
				} else {
					packet[i++]='#';
					packet[i++]=(byte)(ch^0x40);
				}
			} else if((ch&0x7f)=='#') {
				if((i+1)>=MAX_KERMIT_PACKET_SIZE) {
					break;
				} else {
					packet[i++]='#';
					packet[i++]=(byte)ch;
				}
			} else {
				packet[i++]=(byte)ch;
			}
			progress++;
		}
		byte[] arr = new byte[i];
		System.arraycopy(packet,0,arr,0,i);
		fileChunkLength=progress-fileProgress;
		sendPacket(createPacket(arr,KERMIT_FILE_DATA,++sequence));
	}
	public void endOfFile() {
		sendPacket(createPacket(new byte[0],KERMIT_END_OF_FILE,++sequence));
	}
	public void batchEnd() {
		sendPacket(createPacket(new byte[0],KERMIT_END_OF_BATCH,++sequence));
	}
	public void nack() {
		sendPacket(createPacket(new byte[0],KERMIT_NEGATIVE_ACKNOWLEDGE,sequence));
	}
	public void ack() {
		sendPacket(createPacket(new byte[0],KERMIT_ACK,sequence++));
	}
	public boolean setFileName(String name) {
		if(fileLock>0) {
			return(false);
		} else {
			fileName=name;
			return(true);
		}
	}
	public String getFileName() {
		return(fileName);
	}
	public boolean setFileData(byte[] data) {
		if(fileLock>0) {
			return(false);
		} else {
			fileData=data;
			return(true);
		}
	}
	public byte[] getFileData() {
		return(fileData);
	}
	static public byte[] fileToArr(String name) throws java.io.IOException {
		byte[] b = new byte[0x20000];
		File f = new File(name);
		FileInputStream inputStream = new FileInputStream(f);
		inputStream.read(b);
		return(b);
	}
	public int sendFile(String name,byte[] data,int taskId) {
        this.taskId = taskId;
		if(setFileName(name)&&setFileData(data)) {
			fileLock=++fileLockCounter;
			sequence=0;
			initializationHeader();
			return(fileLock);
		} else {
			return(-1);
		}
	}
	public int getFileLock() {
		return(fileLock);
	}
	public void clearFileLock() {
		fileLock=0;
	}
	public int receiveFile(int taskId) {
        this.taskId = taskId;
		if(getFileLock()>0) {
			return(fileLock);
		} else {
			fileLock=++fileLockCounter;
//			fileData=new byte[0];
			sequence=0;
			nack();
			return(fileLock);
		}
	}
}
